package httpServer;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.FileNameMap;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * This class is to manufacture and send a HTTP Response to the connected client
 * it will gather the information of this server and format it into a HTTP Response
 * it can also send the data, in either CHUNKED or UN-CHUNKED
 * 
 * @author Li Weihan, Scott Wendlandt, and Rebecca Chandler
 *
 */
public class Response {
	// File from the request
	private File destination; 
	// Header: content-type
	private String contentType;
	// Header: host
	private String host;
	// Header: content-length
	private String contentLength;
	// Header: server
	private String server;
	// Header: date
	private String date;
	// Header: transfer-encoding
	private String transEncoding;
	// Header: last-modified
	private String lastModi;
	// TRUE if this response will be sent chunked
	private boolean isChunked;
	// Files larger than this many bytes shall be chunked
	private static final int CHUNKING_THRESHOLD=4096; //4096 = 0x1000
	// Carriage Return Line Feed
	private static final String CRLF="\r\n";
	
	/**
	 * Construct all the field of of headers
	 * @param destination The file that is requested
	 */
	public Response(File destination){
		this.destination	= destination;
		this.contentType	= getType();
		this.host			= getLocalIp();
		this.contentLength	= ""+destination.length();
		this.server			= "Java/1.6 "+"("+getOSName()+")";
		this.lastModi		= getDate(new Date(destination.lastModified()));
		this.date			= getDate(new Date());
		this.transEncoding	= getTransEncoding(destination.length());

	}
	
	/**
	 * Get MIME type of the file
	 * @return file MIME type
	 */
	private String getType(){
		FileNameMap fileNameMap = URLConnection.getFileNameMap();
		String type = "";
		try {
			type = fileNameMap.getContentTypeFor(destination.getCanonicalPath());
		} catch (IOException e) {
			System.out.println("Unable to get file MIME type");
		}
		return type;
	}
	
	/**
	 * Get the transfer-encoding header field content
	 * @param length the total in the destination file
	 * @return "chunked" if file>CHUNK, otherwise return empty string
	 */
	private String getTransEncoding(long length){
		String trans;
		if(length>CHUNKING_THRESHOLD){
			trans="chunked";
			isChunked=true;
		}else{
			trans="";
			isChunked=false;
		}
		return trans;
	}
	/**
	 * Get the formatted Date for header
	 * @param date date in milliseconds
	 * @return date in format of Date field
	 */
	private String getDate(Date date){
		SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
		return format.format(date);
	}
	
	/**
	 * Get the host IP
	 * @return host IP
	 */
	private String getLocalIp() {  
		String localIp = null;
		try {  
			localIp = InetAddress.getLocalHost().getHostAddress(); 
		} catch (UnknownHostException e) {  
			System.out.println("Failed to get system IP");
		}  
		return localIp;  
	} 
	
	/**
	 * Get the OS name;
	 * @return OS name;
	 */
	private String getOSName() {  
		return System.getProperty("os.name");  
	}
	
	/**
	 * Get the full header for HTTP Response
	 * @return header section of HTTP Response
	 */
	public String getHeader(){
		return( //"HTTP/1.1 200 OK"								  + CRLF + // Already sent by ServerClientConnection
				"Date: " 				+ this.date    			  + CRLF +
				"Server: "			 	+ this.server 			  + CRLF +
				"Host: "				+ this.host				  + CRLF +
				"Last-Modified: "		+ this.lastModi		   	  + CRLF +
				"Content-Type: "	 	+ this.contentType		  + CRLF +
				(transEncoding.equals("chunked") ? 
				"Transfer-Encoding: "	+ this.transEncoding	  + CRLF:
				"Content-Length: " 		+ this.contentLength	  + CRLF)
						+CRLF );
	}
	
	/**
	 * Send a HTTP response through a specific DataOutputStream
	 * @param data the DataOutputStream that the response will be sent through
	 */
	public void send (OutputStream data){

		InputStream fileInput = null;
		OutputStream fileOutput =null;
		
		try {
			fileInput = new FileInputStream(destination);
			fileOutput = new DataOutputStream(data);   

			// Write header
			fileOutput.write(getHeader().getBytes());
			
			// Make a buffer to hold chunking threshold # of bytes
			byte[] buf = new byte[CHUNKING_THRESHOLD]; 
			// number of bytes read into the buffer
			int read=0;
			
			// Read some bytes while there are still more bytes to read
			while((read=fileInput.read(buf)) != -1){
				if(isChunked) {
					//write hex number of bytes in buffer + CRLF
					fileOutput.write((Integer.toHexString(read)+CRLF).getBytes());
				}
				// Write buffered bytes in array "buf" from index 0 to # read
				fileOutput.write(buf,0,read);
				fileOutput.write(CRLF.getBytes());
				fileOutput.flush();	
			}
			
			// Write closing 0 byte for chunked encoding
			if(isChunked) {
				fileOutput.write(((Integer.toHexString(0)).getBytes()));
				fileOutput.write((CRLF).getBytes());
			}
			fileOutput.write((CRLF).getBytes());
			fileOutput.flush();		
		} catch (FileNotFoundException e) {
			System.out.println("Can't find File");
		} catch (IOException e) {
			System.out.println("Unable to send file, IO error");
		}  
		finally{
			try {
				if(fileInput!=null){
					fileInput.close();					
				}	
				if(fileOutput!=null){
					fileOutput.close();					
				}
			} catch (IOException e) {
				System.out.println("Unable to close file, IO error");
			}   
		}
	}
}
